/**
 *
 */
package org.jprobe.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.jprobe.reflect.classpath.Instantiator;
import org.jprobe.reflect.classpath.InstantiatorInterface;

/**
 * BeanInspectorInterface implementation
 * @author Hussain Al-Mutawa
 * @version 1.0
 */
public class BeanInspector implements BeanInspectorInterface {
//	/** classpath of the project **/
//	private transient static final ClassPathInterface CLASSPATH=ClassPathReader.instance.classpath;
	private static final InstantiatorInterface INSTANTIATOR = new Instantiator();

//	/** the project hosting the bean **/
//	private transient ProjectInterface project;
	/** the bean type which is going to be inspected **/
	private transient Class<?> bean;
	/** an instantiated copy of the bean **/
	private transient Object beanInstance;
	/** a table of the values gained from each getter **/
	private transient final SortedMap<Method, BeanGetterValueInterface>publicMethodsValues;
	/** private/protected/default access methods **/
	private transient final SortedMap<Method, BeanGetterValueInterface>nonPublicMethodsValues;
	/** initializes the bean inspector **/
	public BeanInspector() {
		super();
		final Comparator<Method> comprator = MethodsComparator.instance();
		this.publicMethodsValues=new TreeMap<Method, BeanGetterValueInterface>(comprator);
		this.nonPublicMethodsValues=new TreeMap<Method, BeanGetterValueInterface>(comprator);
	}

	@Override
	public void inspect() throws Exception {
		if(beanInstance==null){
			beanInstance = INSTANTIATOR.getInitializedObject(bean);
		}
		this.publicMethodsValues.clear();
		this.nonPublicMethodsValues.clear();
		final Set<Method>methods=new HashSet<Method>();
		methods.addAll(Arrays.asList(bean.getDeclaredMethods()));
		methods.addAll(Arrays.asList(bean.getMethods()));
		//System.out.println(methods.size());

		for(final Method method:methods){
			if( isNonAbstractNonVoid(method) ){
				//System.out.println(method);
				final BeanGetterValueInterface returnValue = new BeanGetterValue();
				try{
					final Object[] parameters = INSTANTIATOR.getParametersValues(method.getParameterTypes());
					method.setAccessible(true);

					returnValue.setValue(method.invoke(beanInstance, parameters));
				}catch(final Throwable exp){
					if(exp.getCause()==null){
						returnValue.setExcepionThrown( exp.getClass().getName() );
					}else{
						returnValue.setExcepionThrown( exp.getCause().getClass().getName() );
					}
				}
				if(Modifier.isPublic(method.getModifiers())){
					this.publicMethodsValues.put( method , returnValue );
				}else{
					this.nonPublicMethodsValues.put( method , returnValue );
				}
			}
		}
	}

	/**
	 * determines if the method is non-abstract and returns a value
	 * @param method the method to check its modifiers
	 * @return true if the method is declared public (return value) and has definition, false otherwise
	 */
	public static boolean isNonAbstractNonVoid(final Method method){
		final int modifiers = method.getModifiers();
		return !Modifier.isAbstract(modifiers)
			&&  method.getReturnType()!=Void.TYPE
			&&  method.getDeclaringClass()!=Object.class
			&&  !("$VRi".equals(method.getName()) && method.toString().contains("boolean[][]"));
	}

	@Override
	public void reset() {
		this.publicMethodsValues.clear();
		this.beanInstance = null;
	}

	@Override
	public Class<?>getBean(){
		return bean;
	}

	@Override
	public void setBean(final Class<?>bean){
		this.bean=bean;
		this.reset();
	}

	@Override
	public BeanGetterValueInterface get(final Method method) {
		final BeanGetterValueInterface result;
		if(publicMethodsValues.containsKey(method)){
			result = this.publicMethodsValues.get(method);
		}else{
			result = this.nonPublicMethodsValues.get(method);
		}
		return result;
	}
	/**
	 * gets the bean instance
	 * @return bean instance
	 */
	public Object getBeanInstance() {
		return beanInstance;
	}

	@Override
	public Method[] getPublicMethods() {
		return publicMethodsValues.keySet().toArray(new Method[publicMethodsValues.size()]);
	}

	@Override
	public Method[] getPrivateMethods() {
		return this.nonPublicMethodsValues.keySet().toArray(new Method[this.nonPublicMethodsValues.size()]);
	}

	@Override
	public Method[] getMethods() {
		final List<Method>list=new LinkedList<Method>();
		list.addAll(publicMethodsValues.keySet());
		list.addAll(nonPublicMethodsValues.keySet());
		return list.toArray(new Method[list.size()]);
	}
	@Override
	public BeanGetterValueInterface get(final String methodToString) {
		BeanGetterValueInterface bgv = null;
		final Method[] methods=getMethods();
		for(final Method method:methods){
			if(methodToString.equals(method.toString())){
				bgv=get(method);
				break;
			}
		}
		return bgv;
	}

	public void setBeanInstance(final Object beanInstance) {
		this.beanInstance = beanInstance;
	}
}
